#include "pong.h"
#include "sound.h"
#include <linux/fb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <pthread.h>
#include <errno.h>

int driver;
const char *driver_path = "/dev/led_button_driver";

int curr_sound = 0;

void* sound_thread_func(void *arg)
{
  playSound(curr_sound);
}

pthread_t sound;

int fbfd = 0;
char *fbp = 0;
int screensize;
char backBuffer[307200];


Entity ball01 = {160, 120, 160, 120, 6, 6, 1, 1};
Entity *ball_ptr = &ball01;
 
Entity leftPaddle = {10, 110, 10, 110, 6, 20, 0, 0};
Entity *leftPaddle_ptr = &leftPaddle;

Entity rightPaddle = {304, 110, 304, 110, 6, 20, 0, 0};
Entity *rightPaddle_ptr = &rightPaddle;

short exiting = 0;
int timer = 1000;
int delay = 100000;
short flushScreen = 0;

short scoreLeft = 0;
short scoreRight = 0;


int main()
{
  printf("Starting\n");
  fbfd = open("/dev/fb0", O_RDWR);
  
  screensize = WIDTH * HEIGHT * 4;
  fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
  initSound();
  OpenDriver();
  memset(fbp,0,screensize);
  SetLeds(36);
  ResetScreen();
  while(!exiting)
  {
    timer--;
    Update();
    Draw();
    RectUpdateBB(ball_ptr);
    RectUpdateBB(leftPaddle_ptr);
    RectUpdateBB(rightPaddle_ptr);
    //Busy wait
    while(delay > 0)
      delay--;
    delay = 100000;
    
  }
  WinScreen();
  
  //deallocate and exit
  pthread_join(sound, NULL);
  pthread_cancel(sound);

  munmap(fbp, screensize);
  printf("Closing\n");
  close(fbfd);
  closeSound();
  return 0;
}

void FlushBackBuffer()
{
  int i = 0;
  for(i = 0; i < screensize; i++)
    fbp[i] = backBuffer[i];
}

void WriteNumber(short x, short y, short num)
{
  int i;
  int j;

  for(j = 0; j < 5; j++)
  {
    for(i = 0; i < 4; i++)
    {
      if(Numbers[(num * 5) + j][i] == 1)
        DrawPixel(x + i, y + j, 0);
    }
  }
}

void WritePlayer(short x, short y)
{
  int i;
  int j;
  for(j = 0; j < 5; j++)
  {
    for(i = 0; i < 27; i++)
    {
      if(PlayerSprite[j][i] == 1)
        DrawPixel(x + i, y + j, 0);
    }
  }
}

void WriteAI(short x, short y)
{
  int i;
  int j;
  for(j = 0; j < 5; j++)
  {
    for(i = 0; i < 8; i++)
    {
      if(AISprite[j][i] == 1)
        DrawPixel(x + i, y + j, 0);
    }
  }
}

void WriteWon(short x, short y)
{
  int i;
  int j;
  for(j = 0; j < 5; j++)
  {
    for(i = 0; i < 15; i++)
    {
      if(WonSprite[j][i] == 1)
        DrawPixel(x + i, y + j, 0);
    }
  }
}

void WinScreen()
{
  memset(backBuffer, 0, screensize);
  if(scoreRight > scoreLeft)
    WriteAI(151, 120);
  else
    WritePlayer(132, 120);

  WriteWon(162, 120);
  
  curr_sound = 1;
  pthread_create(&sound, NULL, sound_thread_func, NULL);
  FlushBackBuffer();
  
}

void ResetScreen()
{
  int i = 0;
  for(i = 0; i < 4; i++)
  {
    int delay = 1000000;
    SetLeds(255);
    while(delay > 0)
    {
      delay--;
    }
    SetLeds(0);
    delay = 1000000;
    while(delay > 0)
    delay--;
  }
  SetLeds(36);

  ball_ptr->speedX = 1;
  int rndNum = rand() % 5;
  ball_ptr->speedY = 2 - rndNum;
  ball_ptr->posX = 160;
  ball_ptr->posY = 120;
  ball_ptr->lastPosX = 160;
  ball_ptr->lastPosY = 120;

  leftPaddle_ptr->posY = 110;
  leftPaddle_ptr->lastPosY = 110;
  leftPaddle_ptr->speedY = 0;

  rightPaddle_ptr->posY = 110;
  rightPaddle_ptr->lastPosY = 110;
  rightPaddle_ptr->speedY = 0;

  WritePlayer(3, 2);
  WriteNumber(33, 2, scoreLeft);
  WriteAI(301, 2);
  WriteNumber(311, 2, scoreRight);

  FlushBackBuffer();
}

void RectUpdateBB(Entity *entity)
{
  short x;
  short y;
  short width;
  short height;
  short deltaX;
  short deltaY;

  deltaX = entity->posX - entity->lastPosX;
  deltaY = entity->posY - entity->lastPosY;

  if(deltaX >= 0)
  {
    x = entity->lastPosX;
    width = deltaX + entity->width;
  }
  else
  {
    x = entity->posX;
    width = entity->width - deltaX;
  }

  if(deltaY >= 0)
  {
    y = entity->lastPosY;
    height = deltaY + entity->height;
  }
  else
  {
    y = entity->posY;
    height = entity->height - deltaY;
  }

  int locW = width * 4;
  x *= 4;
  int i = 0;
  int j = 0;
  int loc;
  for(j = 0; j < height; j++)
  {
    for(i = 0; i < locW; i++)
    {
      loc = x + i + ((y + j) * 1280);
      fbp[loc] = backBuffer[loc];
    }
  }
  


}


void Update()
{
  //storing last position of the ball
  ball_ptr->lastPosX = ball_ptr->posX;
  ball_ptr->lastPosY = ball_ptr->posY;

  //storing last positions of the paddles
  leftPaddle_ptr->lastPosY = leftPaddle_ptr->posY;
  rightPaddle_ptr->lastPosY = rightPaddle_ptr->posY;


  //updating position according to speed
  ball_ptr->posX += ball_ptr->speedX;
  ball_ptr->posY += ball_ptr->speedY;

  RPaddleAI();
  int buttonMask = ReadButtons();
  if(buttonMask)
  {
    if(buttonMask == 2)
      leftPaddle_ptr->speedY = 2;
    else if(buttonMask == 5)
      leftPaddle_ptr->speedY = -2;
  }
  else
  {
    leftPaddle_ptr->speedY = 0;
  }
  //updating paddles
  rightPaddle_ptr->posY += rightPaddle_ptr->speedY;
  leftPaddle_ptr->posY += leftPaddle_ptr->speedY;
  
  //make sure paddles stay within the screen
  if(rightPaddle_ptr->posY < 8)
    rightPaddle_ptr->posY = 8;
  if(leftPaddle_ptr->posY < 8)
    leftPaddle_ptr->posY = 8;
  if(rightPaddle_ptr->posY + rightPaddle_ptr->height > 240)
    rightPaddle_ptr->posY = 240 - rightPaddle_ptr->height;
  if(leftPaddle_ptr->posY + leftPaddle_ptr->height > 240)
    leftPaddle_ptr->posY = 240 - leftPaddle_ptr->height;
  
    //Collide top
    if(ball_ptr->posY <= 7)
    {
      curr_sound = 2;
      pthread_create(&sound, NULL, sound_thread_func, NULL);
      ball_ptr->posY = 7;
      ball_ptr->speedY = -ball_ptr->speedY;
    }
  
    //Collide bottom
    if(ball_ptr->posY + ball_ptr->height >= HEIGHT)
    {
      curr_sound = 2;
      pthread_create(&sound, NULL, sound_thread_func, NULL);
      ball_ptr->posY = HEIGHT - ball_ptr->height;
      ball_ptr->speedY = -ball_ptr->speedY;
    }

    //Collide left
    if(ball_ptr->posX <= 0)
    {
      scoreRight++;
      if(scoreRight > 5)
        exiting = 1;
      memset(backBuffer, 0, screensize);
      ResetScreen();
    }

    //Collide right
    if(ball_ptr->posX >= WIDTH - ball_ptr->width)
    {
      scoreLeft++;
      if(scoreLeft > 5)
        exiting = 1;
      memset(backBuffer, 0, screensize);
      ResetScreen();
    }

    //Collision left paddle
    if(ball_ptr->posX <= leftPaddle_ptr->posX + leftPaddle_ptr->width &&
       ball_ptr->posX + ball_ptr->width >= leftPaddle_ptr->posX &&
       ball_ptr->posY <= leftPaddle_ptr->posY + leftPaddle_ptr->height &&
       ball_ptr->posY + ball_ptr->height >= leftPaddle_ptr->posY )
      {
        curr_sound = 3;
        pthread_create(&sound, NULL, sound_thread_func, NULL);
        //Collision l/r
        if(ball_ptr->posY  >= leftPaddle_ptr->posY &&
           ball_ptr->posY + ball_ptr->height <= 
           leftPaddle_ptr->posY + leftPaddle_ptr->height)
        {
          
          ball_ptr->speedX = -ball_ptr->speedX;
        }
        else
        {
          ball_ptr->speedX = 2;
          ball_ptr->posX = leftPaddle_ptr->posX + leftPaddle_ptr->width;
          ball_ptr->speedY = leftPaddle_ptr->speedY;
       }
    }
    //Collision right paddle
    if(ball_ptr->posX <= rightPaddle_ptr->posX + rightPaddle_ptr->width &&
       ball_ptr->posX + ball_ptr->width >= rightPaddle_ptr->posX &&
       ball_ptr->posY <= rightPaddle_ptr->posY + rightPaddle_ptr->height &&
       ball_ptr->posY + ball_ptr->height >= rightPaddle_ptr->posY )
      {
        curr_sound = 3;
        pthread_create(&sound, NULL, sound_thread_func, NULL);
        //Collision l/r
        if(ball_ptr->posY  >= rightPaddle_ptr->posY &&
           ball_ptr->posY + ball_ptr->height <= 
           rightPaddle_ptr->posY + rightPaddle_ptr->height)
        {
          ball_ptr->speedX = -ball_ptr->speedX;
          ball_ptr->speedY = 2 - (rand() % 5);
        }
        else
        {
          ball_ptr->speedX = -2;
          ball_ptr->posX = rightPaddle_ptr->posX - ball_ptr->width;
          ball_ptr->speedY = rightPaddle_ptr->speedY;
        }
      }
    
}

void RPaddleAI()
{
  short deltaY;
  if(ball_ptr->posX > 260)
  {
    deltaY = ball_ptr->posY - (rightPaddle_ptr->posY + (rightPaddle_ptr->height / 2));
    
    if(deltaY < 0)
    {
      if(deltaY < -MAX_AI_SPEED)
        rightPaddle_ptr->speedY = -MAX_AI_SPEED;
      else
        rightPaddle_ptr->speedY = deltaY;
    }
    else if(deltaY > 0)
    {
      if(deltaY > MAX_AI_SPEED)
        rightPaddle_ptr->speedY = MAX_AI_SPEED;
      else
        rightPaddle_ptr->speedY = deltaY;
    }
    else
    {
      rightPaddle_ptr->speedY = 0;
    }
    
  }
  else
  {
    rightPaddle_ptr->speedY = 0;
  }
}

void Draw()
{
  DrawBall(ball_ptr, 0);
  DrawPaddle(leftPaddle_ptr, 0);
  DrawPaddle(rightPaddle_ptr, 0);
}

void DrawPixel(short x, short y, short isBlack)
{
  char drawColor = 0;

  if(!isBlack)
    drawColor = 255;

  int loc = (x * 4) + (y * 1280);
  backBuffer[loc + 1] = drawColor;
  backBuffer[loc + 2] = drawColor;
  backBuffer[loc + 3] = drawColor;
}

void DrawBall(Entity *ball, short isErasing)
{
  int i = 0;
  int j = 0;
  //Erase old pixels
  for(j = 0; j < ball->height ;j++)
  {
    for(i = 0; i < ball->width ;i++)
    {
      if(BallSprite[i][j] == 1)
      {
        DrawPixel(ball->lastPosX + i, ball->lastPosY + j, 1);
      }
    }
  }  

  //Draw new pixels
  for(j = 0; j < ball->height ;j++)
  {
    for(i = 0; i < ball->width ;i++)
    {
      if(BallSprite[i][j] == 0)
        {
          DrawPixel(ball->posX + i, ball->posY + j, 1);
        }
      else
        {
          DrawPixel(ball->posX + i, ball->posY + j, 0);
        }
    }
  }
}

void DrawPaddle(Entity *paddle, short isErasing)
{
  int i = 0;
  int j = 0;
   
  for(j = 0; j < paddle->height; j++)
  {
    for(i = 0; i < paddle->width; i++)
    {
      DrawPixel(paddle->lastPosX + i, paddle->lastPosY + j, 1);
    }
  }

  for(j = 0; j < paddle->height; j++)
  {
    for(i = 0; i < paddle->width; i++)
    {
      DrawPixel(paddle->posX + i, paddle->posY + j, 0);
    }
  }
}

int OpenDriver(){
  driver = open(driver_path,O_RDWR);
  if(driver == -1){
    printf("led_button_driver could not be opened\n");
    fprintf(stderr,"error: %s.\n",strerror(errno));
    return -1;
  }
  return 0;
}

int ReadButtons(){
  int result;
  read(driver,&result,sizeof(result));
  if(result == 0){
    return 0;
  }
  switch(result){
    case 4: return 2; break;
    case 32: return 5; break;
    default: return 0;
  }
}

void SetLeds(int num){
  write(driver,&num,sizeof(num));
}
